package cc.telecomdigital.tdstock.Services;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;

import com.lightstreamer.ls_client.ConnectionInfo;
import com.lightstreamer.ls_client.ConnectionListener;
import com.lightstreamer.ls_client.LSClient;
import com.lightstreamer.ls_client.PushConnException;
import com.lightstreamer.ls_client.PushServerException;
import com.lightstreamer.ls_client.PushUserException;
import com.lightstreamer.ls_client.SubscrException;
import com.lightstreamer.ls_client.UpdateInfo;

import android.content.Context;
import cc.telecomdigital.tdstock.Framework.UniqueItem.TDStockLog;
import cc.telecomdigital.tdstock.Services.LS_Authentication.ILS_Authentication_Report;
import cc.telecomdigital.tdstock.Services.LS_Authentication.LS_Authentication_Result;
import cc.telecomdigital.tdstock.Services.LS_Authentication.LS_Authentication_ResultExt;
import cc.telecomdigital.tdstock.Services.LS_ClientSubscriberHandler.ILS_ClientSubscriber_Response;
import cc.telecomdigital.tdstock.Services.LS_ClientSubscriberHandler.ILS_ConnectionListener;
import cc.telecomdigital.tdstock.Services.LS_GetServerTime.ILS_GetServerTimeReport;
import cc.telecomdigital.tdstock.Services.LS_GetServerTime.LS_GetServerTime_Result;
import cc.telecomdigital.tdstock.Tools.ReporterTicker.IReporterTicker_Report;

// A complete Client Control
public class LS_UserClient implements ILS_Authentication_Report,
	ConnectionListener, ILS_ConnectionListener, ILS_ClientSubscriber_Response
{
	public class LS_UserClient_Authentication_Result extends LS_Authentication_Result
	{
		public boolean	successfulSend	= false;

		public LS_UserClient_Authentication_Result(boolean iSuccessfulSend,
										LS_Authentication_Result iResult)
		{
			super(iResult);
			successfulSend = iSuccessfulSend;
		}
	}

	// Report to user about login status
	public interface ILS_UserClient_Authentication_Report
	{
		void LS_UserClient_Authentication_Response(
				LS_UserClient_Authentication_Result iResponse);
	}

	/** NOT_REQUEST = not initialized, basically requires a normal login procedure with a ready-received session key
	   CONNECTED = connected
	   CONNECTION_FAIL = Try to establish a connection, but report as fail, either network problem, host not found, permission and etc.
	   CONNECTION_REJECT = Cannot connect to server, reject on logon
	   CONNECTION_INVALID_USER = Invalid User name
	   CONNECTION_INVALID_PASSWD = Invalid Password
	   CLOSE_ON_REST = System suspend to close connection
	   CLOSE_BY_FORCE = request to close connection by server
	   CLOSE_BY_CONNECTION_DROP = went into a no network place or network drop
	   CLOSE_BY_REQUEST = request from a module
	   CLOSE_BY_SWITCHING = a close call on switch to info url and will be back in a moment **/
	public enum CONNECTION_STATUS { NOT_REQUEST, CONNECTED, CONNECTION_FAIL, 
							  CONNECTION_REJECT, CONNECTION_INV_USER, CONNECTION_INV_PASSWD,
							  CLOSE_ON_REST,
							  CLOSE_BY_FORCE, CLOSE_BY_CONNECTION_DROP, 
							  CLOSE_BY_REQUEST, CLOSE_BY_SWITCHING}
	
	/** Response Call back of update of data, with the identifier **/
	public interface ILS_UserClient_Response extends ILS_ClientSubscriber_Response, ILS_UserClient_ConnectionListener, IReporterTicker_Report
	{
		// Intention to leave empty
	}
	
	/** Response Call back for Client Connection Change **/
	public interface ILS_UserClient_ConnectionListener
	{
		/** Report of the current LS Client Status **/
		void LS_UserClient_ConnectionReport(CONNECTION_STATUS iStatus);
	}
	
	private boolean					debugMode	= false;

	/** The delay time to get status connection after calling openConnection, 
         300 millisecond to get the connection status for ls client **/
	//private final 						 int statusDelayTime = 300;
	//Account Number
	LS_AuthenticationReport                      lsReqLogonInfo;
	//User Report
	private ILS_UserClient_Authentication_Report	authenReport;
	//Activity Owner
	private Context						ownerContext;

	private CONNECTION_STATUS                    connectStatus;
	
	private boolean                              freeTrialReq;

	//Prevent multiple access	
	private Object padLock		= new Object();
	//Prevent list modification during access
	private Object listLock       = new Object();
	//prevent lsClient modification during access
	private Object clientLock     = new Object();
	
	private class LS_AuthenticationReport
	{
		public String  loginAcct;
		public String  loginAcctName;  //For internal Logon to light Streamer
		public String  acctPassword;
		public String  errorMessage;
		public String  horseMessage;
		public String  footballMessage;
		public String  svcPlan;
		public String  svcFlag;
		public String  lsServerTime;
		public boolean unLimiteStreaming;
		public boolean isBMPMode;
		
		public LS_AuthenticationReport()
		{
			FlushInfo();
		}
		
		private void FlushInfo()
		{
			loginAcct = "";
			loginAcctName = "";
			acctPassword = "";
			horseMessage = "";
			footballMessage = "";
			svcPlan = "";
			svcFlag = "";
			errorMessage = "";		
			lsServerTime = "";
			unLimiteStreaming = false;
			isBMPMode 	   = false;
		}
				
		public void SetAccountInfo(String iAccount, String iAccountName, String iPassword)
		{
			loginAcct = iAccount;
			loginAcctName = iAccountName;
			acctPassword = iPassword;
		}
		
		public void SetServicePlanInfo(String iServicePlan, String iServiceFlag)
		{
			svcPlan = iServicePlan;
			svcFlag = iServiceFlag;
			
			unLimiteStreaming = false;
			isBMPMode         = false;
			if (iServicePlan.length() >= 11)
			{
				String flagValue;
				flagValue = iServicePlan.substring(10, 11);
				if (flagValue.equalsIgnoreCase("Z"))
					unLimiteStreaming = true;												
			}
						
			if (iServicePlan.length() >= 15)
			{
				String flagValue;
				flagValue = iServicePlan.substring(14, 15);
				if (flagValue.equalsIgnoreCase("1"))
					isBMPMode = true;												
			}
			
			if (debugMode)
			{
				TDStockLog.i(LOG_TAG, "iServicePlan: " + iServicePlan);
				TDStockLog.i(LOG_TAG, "iServiceFlag: " + iServiceFlag);				
				TDStockLog.i(LOG_TAG, "Service Streaming unlimited: " + unLimiteStreaming + ", BMPMode: " + isBMPMode);
			}				
		}
		
		public void SetPromotionMessage(String iHorseMsg, String iFBMsg)
		{
			horseMessage = iHorseMsg;
			footballMessage = iFBMsg;
		}
		
		public void SetErrorMessage(String iMessage)
		{
			errorMessage = iMessage;
		}
		
		public void SetLSServerTime(String iServerTime)
		{
			lsServerTime = iServerTime;
		}
		
	}
	
	// *** Authentication ***
	public LS_UserClient(Context iContext, ILS_UserClient_Response iSubReport)
	{
		// A lock object to prevent for multiple call
		ownerContext = iContext;
		lsReqLogonInfo = new LS_AuthenticationReport();
		InitClientControl(iSubReport);	
	}
	
	/** The account name that connect to Light Streamer Client **/
	public String GetAcctName()
	{
		return lsReqLogonInfo.loginAcct;
	}
	
	public String GetAcctLogonName()
	{
		return lsReqLogonInfo.loginAcctName;
	}	
	
	/** The password to connect Light Streamer Client **/
	public String GetSessionKey()
	{	
		return lsReqLogonInfo.acctPassword;
	}
	
	/** The service from login reply */
	public String GetServicePlan()
	{
		return lsReqLogonInfo.svcPlan;
	}
	
	/** The service flag form login reply */
	public String GetServiceFlags()
	{
		return lsReqLogonInfo.svcFlag;
	}
	
	/** Tell whether the streaming plan has limited minute **/
	public boolean IsStreamingUnLimited()
	{
		return lsReqLogonInfo.unLimiteStreaming;
	}
	
	/** Horse Promotion Message from login reply */
	public String GetHorseMessage()
	{
		return lsReqLogonInfo.horseMessage;
	}	
	
	/** Sport Promotion Message from login reply */	
	public String GetSportMessage()
	{
		return lsReqLogonInfo.footballMessage;
	}	
		
	public String GetErrorMessage()
	{
		return lsReqLogonInfo.errorMessage;
	}
	
	public String GetLSLastConnectTime()
	{
		return lsReqLogonInfo.lsServerTime;
	}	
	
	//Check whether the user is logon as BMP user
	public boolean IsBMPMode()
	{
		return lsReqLogonInfo.isBMPMode;
	}	
	
	//The url should come with all your combination of login
	public void RequestSessionKey(String iURL, String iPortNumber, boolean iFreeTrial,
			ILS_UserClient_Authentication_Report iReport)
	{
		synchronized(padLock)
		{
			// Will try to perform a login feature
			LS_Authentication obtainSession;
	
			authenReport = iReport;
			freeTrialReq = iFreeTrial;
			obtainSession = new LS_Authentication(ownerContext, iURL, iPortNumber);
			obtainSession.RequestLogin(this);
		}
	}

	@Override
	public void ILS_Authentication_Response( boolean iSuccessfulSend,
									 LS_Authentication_ResultExt iResponse)
	{
		LS_UserClient_Authentication_Result reportResult;
		//Should we flush the account info, other might need it
		//lsReqLogonInfo.FlushInfo();
		if (iSuccessfulSend != true)
		{
			if (authenReport != null)
			{
				reportResult = new LS_UserClient_Authentication_Result( false, 
										new LS_Authentication_Result(null, null, null, null));
				authenReport.LS_UserClient_Authentication_Response(reportResult);
			}
			return;
		}
		
		//No data will be passed as null
//		if(iResponse == null || iResponse.statusCode == null) 
//		{
//			//modified by haibo, 2011-09-26 ; to avoid NullPointerException			
//			return;
//		}

		//Save our login
		if (iResponse.statusCode.equals("0"))
		{			
			if ((iResponse.accountNumber.length() <= 0) || (iResponse.sessionKey.length() <= 0))
			{				
				if(debugMode)
					TDStockLog.i(LOG_TAG, "Fail to obtain loginAcct and acctPassword");				
			}
			else
			{	
				String accountLogon;
				
				if (freeTrialReq)
					accountLogon = iResponse.accountNumber + "trial";
				else
					accountLogon = iResponse.accountNumber + "hk";
				
				lsReqLogonInfo.SetAccountInfo(iResponse.accountNumber, accountLogon,  iResponse.sessionKey);
				lsReqLogonInfo.SetServicePlanInfo(iResponse.svcPlan, iResponse.svcFlag);
				lsReqLogonInfo.SetPromotionMessage(iResponse.horseMsg, iResponse.footballMsg);
				if(debugMode)
					TDStockLog.i(LOG_TAG, "Obtain loginAcct and acctPassword");
			}
		}
		
		lsReqLogonInfo.SetErrorMessage(iResponse.errorMessage);
  
		if (authenReport != null)
		{
			reportResult = new LS_UserClient_Authentication_Result( iSuccessfulSend,
								new LS_Authentication_Result( iResponse.statusCode, iResponse.errorMessage, 
														iResponse.linkCaption, iResponse.linkContent));
			authenReport.LS_UserClient_Authentication_Response(reportResult);
		}
	}

	private Hashtable<String, LS_ClientSubscriberHandler>   subReqItemList;	
	
	// Need for LightStreamer Connection
	// Parallel List for user subscription
	// This is not a good way, but we need it to prevent multiple renew
	private boolean                    isControllerFresh;
	private LSClient				lsControlClient;
	//Internal Control
	private final String			LOG_TAG	= "LS_UserClient";
	private ILS_UserClient_Response	subscribeReport;

	/** Initialize to use light streamer client object
	 * @param iSubscribeReport - data reporter, where to when data comes from server
	 * @param iReporter        - connection reporter, report of connection change 
	 */
	private void InitClientControl(ILS_UserClient_Response iSubscribeReport)
	{
		//Only report to the host client, in our case, it would be the application itself
		isControllerFresh = true;
		lsControlClient   = new LSClient();
		//Register to receive data change
		subscribeReport = iSubscribeReport;
		//Register to receiver connection Change
		subReqItemList      = new Hashtable<String, LS_ClientSubscriberHandler>();
		connectStatus       = CONNECTION_STATUS.NOT_REQUEST;
	}

	/** Get the last state of the light streamer connection
	 * @return CONNECTION_STATUS (CONNECTED, NOT_REQUEST, REJECTED and etc...
	 */
	public CONNECTION_STATUS GetLastConnectStatus()
	{
		return connectStatus;
	}
	
	
	private final int lightStreamLogonTrialMax = 2;
	/** Request a light streamer connection
	 * @param iURL  - the information url
	 * @param iPortNum - the port to be used for light streamer
	 * @param iAdapter  - adapter for connection
	 * @return true for successful request
	 */	
	public CONNECTION_STATUS RequestConnection( String iURL, String iPortNum,
									    String iAdapter)
	{
		synchronized(padLock)
		{			
			//already connected
			if (connectStatus == CONNECTION_STATUS.CONNECTED)
				return connectStatus;
			
			if (debugMode)
				TDStockLog.i(LOG_TAG, "RequestConnection: " + iURL + ",Port=" + iPortNum
					 + ",Adpater:" + iAdapter);
				
			CONNECTION_STATUS logonStatus;
			int     trialCount;
			
			trialCount = 0;
			while (true)
			{
				logonStatus = RequestConnectionSub(iURL, iPortNum, iAdapter);
				if (logonStatus != CONNECTION_STATUS.CONNECTED)
				{
					++trialCount;
					if (trialCount < lightStreamLogonTrialMax)
					{
						if (debugMode)
							TDStockLog.i(LOG_TAG, "RequestConnection: Retry performed: " + trialCount);
						try
						{
							Thread.sleep(200);
						}
						catch(Exception ex)
						{
							TDStockLog.e(LOG_TAG, "RequestConnection: Ex=" + ex.toString());
						}					
						continue;	
					}
				}
				break;
			}
			
			return logonStatus;
		}
	}

	//Internal request connection request
	private CONNECTION_STATUS RequestConnectionSub(String iURL, String iPortNum, String iAdapter)
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "RequestConnectionSub: Setting Param for login");
		
		ConnectionInfo connectInfo;

		connectInfo = new ConnectionInfo();
		connectInfo.user = lsReqLogonInfo.loginAcctName;
		connectInfo.password = lsReqLogonInfo.acctPassword;
		connectInfo.pushServerUrl = iURL + ":" + iPortNum;
		connectInfo.adapter = iAdapter;
		
//		/* Timeout for sending a warning prompt */
//		connectInfo.probeWarningMillis = 2000;
//		/* Timeout for sending a disconnection prompt */
//		connectInfo.probeTimeoutMillis = 3000;

		try
		{
			if (debugMode)
				TDStockLog.i(LOG_TAG, "RequestConnectionSub: OpenConnection.");
			
			//Most likely will not happen
			if (lsControlClient == null)
				return CONNECTION_STATUS.CONNECTION_FAIL;
			
			if (debugMode)
				TDStockLog.i(LOG_TAG, "RequestConnectionSub: Attempt for Connection");

			lsControlClient.openConnection(connectInfo, this);
			isControllerFresh = false;

			if (debugMode)
				TDStockLog.i(LOG_TAG, "RequestConnectionSub: OpenConnection Done.");			
		}
		catch (PushConnException ex)
		{			
			TDStockLog.e(LOG_TAG, "PushConnException: " + iAdapter + " Login: " + ex.toString());
			//return CONNECTION_STATUS.CONNECTION_REJECT;
			return CONNECTION_STATUS.CONNECTION_FAIL;
		}
		catch (PushServerException ex)
		{
			TDStockLog.e(LOG_TAG, "PushServerException: " + iAdapter + " Login: " + ex.getErrorCode() 
				  + "," + ex.toString());
			
			int errCode;
			
			errCode = ex.getErrorCode();
			if (errCode == PushServerException.NO_ANSWER)
				return CONNECTION_STATUS.CONNECTION_FAIL;
			
			//if ((errCode == PushServerException.SERVER_REFUSAL) || (errCode == PushServerException.SERVER_TIMEOUT))
			if (errCode == PushServerException.SERVER_REFUSAL)				
				return CONNECTION_STATUS.CONNECTION_REJECT;
			
			return CONNECTION_STATUS.CONNECTION_FAIL;
		}
		catch (PushUserException ex)
		{
			TDStockLog.e(LOG_TAG, "PushUserException: " + iAdapter + " Login: " + ex.getErrorCode() 
					+ "," + ex.toString());
			
			int errCode;
			
			errCode = ex.getErrorCode();
			if (errCode == 1)
				return CONNECTION_STATUS.CONNECTION_INV_PASSWD;
			
			return CONNECTION_STATUS.CONNECTION_REJECT;
		}
		catch (Exception ex)
		{
			//Other exception
			TDStockLog.e(LOG_TAG, "Exception: " + iAdapter + " Login: " + ex.toString());
			return CONNECTION_STATUS.CONNECTION_FAIL;
		}
		
		return CONNECTION_STATUS.CONNECTED;
	}

	/** Disconnect the light streamer client, eventually will go to onClose if it is not connect
	 * @param iCloseAction - an Action will be spread when disconnect is proceeded
	 * @return
	 */
	public boolean Disconnect(CONNECTION_STATUS iCloseAction)
	{
		//If we were not connected, we can not drop
		if (connectStatus != CONNECTION_STATUS.CONNECTED)
			return false;
		
		connectStatus = iCloseAction;		
		
		renewClient();			
		return true;
	}

	/** Override the Disconnect Status with a reason, you could place any close CONNECTION_STATUS
	 * and serve as an alert to be used
	 * @param iCloseAction
	 * @return
	 */
	public boolean OverrideDisconnectStatus(CONNECTION_STATUS iCloseAction)
	{
		//This is from server, you can not pretend you are
		if ((iCloseAction == CONNECTION_STATUS.CLOSE_BY_FORCE) || (iCloseAction == CONNECTION_STATUS.CONNECTION_REJECT))
			return false;
		
		if ((connectStatus != CONNECTION_STATUS.CONNECTED) && (connectStatus != CONNECTION_STATUS.NOT_REQUEST))
		{
			connectStatus = iCloseAction;
			return true;
		}
		
		return false;		
	}
	
	
	private void renewClient()
	{
		//A stupid way to prevent create and delete the controller
		if (isControllerFresh)
		{
			if (debugMode)
				TDStockLog.i(LOG_TAG, "renewClient: Client is already freshed, no need to renew.");
			return;
		}
		
		if (debugMode)
			TDStockLog.i(LOG_TAG, "renew Client: Looking at the list");
		
		lsControlClient.unbatchRequest();
		
		if (subReqItemList.size() != 0)
			ClearAllSubscribeItems();
		
		if (debugMode)
			TDStockLog.i(LOG_TAG, "renew Client: Looking at the list done, and renew controller");
		//We might end with some error, but who cares... 
		// to be saved, we should created a new connection for next time, so I would
		// delete the control here and new it again
		synchronized(clientLock)
		{
			//Enter right after the log
			if (	isControllerFresh)
			{
				if (debugMode)
					TDStockLog.i(LOG_TAG, "renew Client: Renew Controller Done, right after log");				
				return;	
			}
			
			if (lsControlClient != null)
				lsControlClient.closeConnection();

			lsControlClient = null;			
			lsControlClient = new LSClient();			
		}
		isControllerFresh = true;		
		
		if (debugMode)
			TDStockLog.i(LOG_TAG, "renew Client: Renew Controller Done");
	}
	
	/** Subscribe an Item if the iIdentification is unique 
	 * @param iIdentifier - unique ID for this subscription
	 * @param iSubItem - subscription structured item 
	 * @return true for a sent request, false if already defined or no connection
	 */	
	public boolean SubscribeItem(String iIdentifier, LS_SubscriptionItem iSubItem)
	{
		synchronized(padLock)
		{
			if (debugMode)
				TDStockLog.i(LOG_TAG, "SubscribeItem: " + iIdentifier + ", "+ iSubItem.toString());
			
			if (connectStatus != CONNECTION_STATUS.CONNECTED)
				return false;
				
			boolean status;
			LS_ClientSubscriberHandler newHandler;
	
			if (subReqItemList.containsKey(iIdentifier))
			{
				if (debugMode)
					TDStockLog.i(LOG_TAG, "SubscribeItem: " + iIdentifier + " re-defined");
				return false;
			}

			newHandler = new LS_ClientSubscriberHandler(iIdentifier, this, this);

			status = newHandler.SubscribeObject(iSubItem);
			synchronized(listLock)
			{
				if (status == true)
					subReqItemList.put(iIdentifier, newHandler);
			}
			return status;
		}
	}
	
	/** SubscribeItem In a defined Array.  The array size of each parameter must be equal
	 * The bad part is if one of them is failed, we do not know, suggest to use when there is a
	 * network
	 * @param iIdentifier
	 * @param iSubItem
	 * @return
	 */
     public boolean SubscribeItemInBatch(List<String> iIdentifier, List<LS_SubscriptionItem> iSubItem)
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "SubscribeItemInBatch");
		
		if (connectStatus != CONNECTION_STATUS.CONNECTED)
			return false;
				
		int            index, count, runIndex;
		List<Integer>  availabeList;
		String    				   nameTag;
		List<LS_ClientSubscriberHandler> handlerList;	
		LS_ClientSubscriberHandler       handler;		
			
		count 	   = iIdentifier.size();
		availabeList = new ArrayList<Integer>();
		handlerList = new ArrayList<LS_ClientSubscriberHandler>();		
		synchronized(listLock)
		{
			//We add first, then delete it from the list if it fail, this is a backward playaroud			
			for (index = 0; index < count; index++)
			{
				nameTag = iIdentifier.get(index);
				if (subReqItemList.containsKey(nameTag) == false)
				{
					//Store by index
					availabeList.add(index);
					
					handler = new LS_ClientSubscriberHandler(nameTag, this, this);
					handlerList.add(handler);
					
					subReqItemList.put(nameTag, handler);					
					continue;
				}
				
				if (debugMode)
					TDStockLog.i(LOG_TAG, "SubscribeItem: " + nameTag + " re-defined");				
			}
		}

		count = availabeList.size();
		if (count == 0)
			return true;

		try
		{
			List<Thread> threadList = new ArrayList<Thread>();
				
			for (index = 0; index < count; index++)
			{
				runIndex = availabeList.get(index);

				final LS_ClientSubscriberHandler newHandler;
				final String identifier;
				final LS_SubscriptionItem reqItem;

				identifier = iIdentifier.get(runIndex);
				reqItem    = iSubItem.get(runIndex);
				newHandler = handlerList.get(index);

				Thread  threadObj = new Thread()
				{
					public void run()
					{
						if (newHandler.SubscribeObject(reqItem))
							return;
						
						//Remove when the subscription fail
						synchronized (listLock)
						{
							subReqItemList.remove(identifier);								
						}
					}
				};
				threadObj.setName(identifier);
				threadList.add(threadObj);
			}

 			lsControlClient.unbatchRequest();
			if ((count > 1) && (lsControlClient != null))
				lsControlClient.batchRequests(count);	

			for (index = 0; index < count; index++)
				threadList.get(index).start();
		}
		catch (SubscrException e)
		{
			TDStockLog.e(LOG_TAG, "SubscribeItemInBatch=>SubscrException: " + e.toString());
			e.printStackTrace();			
		}
		catch (Exception e)
		{
			TDStockLog.e(LOG_TAG, "SubscribeItemInBatch=>Exception: " + e.toString());
			e.printStackTrace();			
		}			
		finally
		{
			if ((count > 1) && (lsControlClient != null))
			{
				lsControlClient.closeBatch();
				lsControlClient.unbatchRequest();
			}
		}

		return true;
	}	
	
		
	/** Un-subscribe an Item if the iIdentification exists within our control 
	  * This un-subscribe function requires network connection.  If it is not
	  * connected to host, the un-subscribe wont work
	  * @param iIdentifier = pre-defined and subscribed identifier
	  * @return true for a sent request, false if already defined or no connection	    
	  */
	public boolean UnsubscribleItem(String iIdentifier)
	{
		//synchronized(padLock)   //modified by haibo in 2012-03-14 for avoiding anr
		{
			if (debugMode)
				TDStockLog.i(LOG_TAG, "UnsubscribleItem");
				
			return RemoveFromList(iIdentifier, true);
		}
	}

	private boolean RemoveFromList(String iIdentifier, boolean iCheckConnection)
	{
//		if (iCheckConnection)
//		{
//			if (connectStatus != CONNECTION_STATUS.CONNECTED)
//				return false;
//		}

		synchronized(listLock)
		{
			//Remove from list
			if (subReqItemList.containsKey(iIdentifier) == false)
				return false;

			subReqItemList.get(iIdentifier).Dispose();
			subReqItemList.remove(iIdentifier);
			return true;
		}
	}
	
	/** Clear the iIdentifier from our list 
	 *  @param iIdentifer - unique ID for subscription
	 *  @return
	 */
	public boolean ClearIdentifier(String iIdentifier)
	{		
		//synchronized(padLock) //modified by haibo in 2012-03-14 for avoiding anr
		{
			if (debugMode)
				TDStockLog.i(LOG_TAG, "ClearIdentifier");
			
			return RemoveFromList(iIdentifier, false);
		}
	}
	
	/** Clear a list which is create internally.
	 * @param iClearList - a list that has the handler which is located in our memory
	 */
	private void ClearSubscriberList(final List<LS_ClientSubscriberHandler> iClearList)
	{
		final int count;
		
		count = iClearList.size();
		if (count == 0)
			return;

//		if (connectStatus != CONNECTION_STATUS.CONNECTED)
//			return;

		if (debugMode)
		{
			if (count > 1)
				TDStockLog.i(LOG_TAG, "ClearSubscriberList(BatchMode): Size=" + count);
			else
				TDStockLog.i(LOG_TAG, "ClearSubscriberList: Size=" + count);			
		}
		
		Thread internalThreadObj = new Thread()
		{
			public void run()
			{
				//Transfer to some where else
				int index;
				
				try
				{
					List<Thread> threadList = new ArrayList<Thread>();
					for (index = 0; index < count; index++)
					{								
						final LS_ClientSubscriberHandler handler;
						
						handler = iClearList.get(index);
						
						Thread  threadObj = new Thread()
						{	
							public void run()
							{	
								if (connectStatus == CONNECTION_STATUS.CONNECTED)   // added by haibo in 2012-03-14
								handler.Dispose();
							}
						};
						threadObj.setName(handler.GetIdentifier());
						threadList.add(threadObj);							
					}

					if ((count > 1) && (lsControlClient != null))	
						lsControlClient.batchRequests(count);

					for (index = 0; index < count; index++)
						threadList.get(index).start();
				}
				catch (SubscrException e)
				{			
					e.printStackTrace();				
					if (debugMode)
						TDStockLog.e(LOG_TAG, "ClearSubscriberList: " + e.toString());		
				}
				catch(Exception e)
				{
					e.printStackTrace();				
					if (debugMode)
						TDStockLog.e(LOG_TAG, "ClearSubscriberList: " + e.toString());
				}		
				finally
				{
					if ((count > 1) && (lsControlClient != null))
					{
						if (connectStatus == CONNECTION_STATUS.CONNECTED)   // added by haibo in 2012-03-14  
						{
							lsControlClient.closeBatch();				
							lsControlClient.unbatchRequest();
						}
						
					}				
				}
			}
		};
		
		internalThreadObj.start();
	}	
	
	/** Internally clear all subscription in the list
	    Un-subscription command sent as possible. 
	    And clear all the subscribed items in the list, no matter what **/	 
	public void ClearAllSubscribeItems()
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "ClearAllSubscribeItems: All items for this client");	

		List<LS_ClientSubscriberHandler>  clearList;

		//Copy all list from the global list
		clearList    = new ArrayList<LS_ClientSubscriberHandler>();
		synchronized(listLock)
		{
			Enumeration<LS_ClientSubscriberHandler> list;
			
			list = subReqItemList.elements();
			while(list.hasMoreElements())
				clearList.add(list.nextElement());

			subReqItemList.clear();
		}

		//synchronized(padLock) //modified by haibo in 2012-03-14 for avoiding anr
		{
			ClearSubscriberList(clearList);
		}
	}	
	
	public boolean ClearIdentifierList(List<String> iIdentifier)
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "ClearIdentifierList Enter");

		int            index, count;
		List<LS_ClientSubscriberHandler>  clearList;
		String                            idString;
		List<String>				    nameList;
		
		nameList = new ArrayList<String>();
		//Create  sublist with filtering for available item
		count 	   = iIdentifier.size();
		clearList    = new ArrayList<LS_ClientSubscriberHandler>();		
		synchronized (listLock)
		{
			if (subReqItemList.size() != 0)
			{
				for (index = 0; index < count; index++)
				{
					idString = iIdentifier.get(index);
					if (subReqItemList.containsKey(idString))
					{
						if (debugMode)
							nameList.add(idString);
	
						clearList.add(subReqItemList.get(idString));
						subReqItemList.remove(idString);
						continue;
					}
	
					if (debugMode)
						TDStockLog.i(LOG_TAG, "ClearIdentifierList: " + idString + " not defined");
				}
			}
			else
			{
				if (debugMode)
					TDStockLog.i(LOG_TAG, "ClearIdentifierList: The list is empty");
			}
		}

		if (debugMode && (nameList.size() > 0))
		{
			String debugLine;

			debugLine = "ClearIdentifierList: ";
			for (String idName : nameList)
				debugLine += idName + ",";

			TDStockLog.i(LOG_TAG, debugLine);

			debugLine = "Remain in List: ";
			synchronized(listLock)
			{
				Set<String> list;

				list = subReqItemList.keySet();
				for (String idName : list)
					debugLine += idName + ",";
			}
			TDStockLog.i(LOG_TAG, debugLine);				
		}

	//	synchronized(padLock) //modified by haibo in 2012-03-14 for avoiding anr
		{
			ClearSubscriberList(clearList);
		}
		return true;
	}
	
	/** When warning received from light streamer client object, lost connection warning **/
	@Override
	public void onActivityWarning(boolean arg0)
	{
		// Will take some time before calling onClose
		TDStockLog.i(LOG_TAG, "onActivityWarning: " + arg0);
	}

	/** When closed received from light streamer client object, close connection **/	
	@Override
	public void onClose()
	{			
		if (debugMode)
			TDStockLog.i(LOG_TAG, "onClose: " + connectStatus.toString());
		
		onCloseConnection();
	}
	
	private void onCloseConnection()
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "onCloseConnection: " + connectStatus.toString());
		
		//Change the connection status if this is a sudden drop, not by a request from our client
		if (connectStatus == CONNECTION_STATUS.CONNECTED)
		{
			if (debugMode)
				TDStockLog.i(LOG_TAG, "onCloseConnection: a sudden drop");	
			connectStatus = CONNECTION_STATUS.CLOSE_BY_CONNECTION_DROP;	
			
		}
		renewClient();
		ExportConnectionStatus(connectStatus);		
	}

	/** When connection is established **/
	@Override
	public void onConnectionEstablished()
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "onConnectionEstablished: ");
		
		connectStatus = CONNECTION_STATUS.CONNECTED;		
		//We need to get the time stamp before we consider it as finish
		synchronized(padLock)
		{
			LS_GetServerTime serverTimeReq;
			String           urlString;
		
			urlString = LS_ClientParamMemory.Get_LSTimeStampURL(lsReqLogonInfo.loginAcctName, lsReqLogonInfo.acctPassword);
			serverTimeReq = new LS_GetServerTime(ownerContext, urlString, LS_ClientParamMemory.Get_LSTimeStampPort());
			serverTimeReq.RequestData(new ILS_GetServerTimeReport()
			{
				@Override
				public void ILS_GetServerTime_Response(boolean iSuccessfulSend,
												LS_GetServerTime_Result iResult)
				{	
					if (iResult.timeStamp.equals("") == false)
					{
						if(debugMode)
							TDStockLog.i(LOG_TAG, "ILS_GetServerTime_Response: " + iResult.timeStamp);
						lsReqLogonInfo.SetLSServerTime(iResult.timeStamp);
					}
					else
					{
						//At least set an invalide time
						lsReqLogonInfo.SetLSServerTime("");						
					}					
				}
			});
		}
       	ExportConnectionStatus(connectStatus);		
	}

	@Override
	public void onDataError(PushServerException arg0)
	{
		TDStockLog.e(LOG_TAG, "onDataError: " + arg0);
	}

	@Override
	public void onEnd(int arg0)
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "onEnd: " + arg0);
	}

	@Override
	public void onFailure(PushServerException arg0)
	{
		TDStockLog.e(LOG_TAG, "onFailure->PushServerException: " + arg0);
		//connectStatus = CONNECTION_STATUS.CLOSE_BY_REJECT;
		connectStatus = CONNECTION_STATUS.CLOSE_BY_CONNECTION_DROP;		
		onCloseConnection();		
	}

	@Override
	public void onFailure(PushConnException arg0)
	{
		TDStockLog.e(LOG_TAG, "onFailure->PushConnException: " + arg0);
		connectStatus = CONNECTION_STATUS.CLOSE_BY_CONNECTION_DROP;
		onCloseConnection();
	}

	@Override
	public void onNewBytes(long arg0)
	{
//		if (debugMode)
//			TDStockLog.i(LOG_TAG, "onNewBytes: " + arg0);
	}

	@Override
	public void onSessionStarted(boolean arg0)
	{
		if (debugMode)
			TDStockLog.i(LOG_TAG, "onSessionStarted: " + arg0);	
	}

	private CONNECTION_STATUS prevStatus = CONNECTION_STATUS.NOT_REQUEST;	
	/** Export the imported status to our connection status listener **/
	private void ExportConnectionStatus(CONNECTION_STATUS iStatus)
	{
		if (subscribeReport == null)
			return;
		
		//Prevent multiple update
		if (prevStatus == iStatus)
			return;
		
		prevStatus = iStatus;
						
		try
		{
			if (debugMode)
				TDStockLog.i(LOG_TAG, "ExportConnectionStatus: " + iStatus.toString());			
			subscribeReport.LS_UserClient_ConnectionReport(iStatus);
		}
		catch (Exception ex)
		{
			TDStockLog.e(LOG_TAG, "onFailure: " + ex.toString());
		}
	}
	
	
	/** Export the data to our interested data listener
	  * The data would be passed to a thread to execute
	  * since the update might run in fast speed **/
	@Override
	public void onUpdate(String iIdentifier, int iSchemaIndex,
			String iSchemaValue, UpdateInfo iUpdateInfo)
	{
		//synchronized(padLock)
		{
			try
			{
				//Pass to our our main control, the Application
				subscribeReport.onUpdate(iIdentifier, iSchemaIndex,
						iSchemaValue, iUpdateInfo);
			}
			catch (Exception ex)
			{
				TDStockLog.e(LOG_TAG, "Error onUpdateExecution: "
						+ iIdentifier + " " + ex.toString());
			}
		}
	}
	
	@Override
	public boolean IsConnected()
	{
		if (connectStatus == CONNECTION_STATUS.CONNECTED)
			return true;
		
		return false;
	}
	
	/** Get the actual light streamer client object used within **/
	@Override
	public LSClient GetClient()
	{
		return lsControlClient;
	}
	
	public void Dispose()
	{
		synchronized(clientLock)
		{
			if (lsControlClient != null)
			{
				if (connectStatus == CONNECTION_STATUS.CONNECTED)
					lsControlClient.closeConnection();
				connectStatus = CONNECTION_STATUS.CLOSE_BY_FORCE;
				lsControlClient = null;
			}
			
			subscribeReport = null;			
			synchronized(listLock)
			{
				if (subReqItemList != null)
				{
					subReqItemList.clear();
					subReqItemList = null;
				}
			}
			
			if (debugMode)
				TDStockLog.i(LOG_TAG, "Object has no reference anymore");
		}			
	}	
}
